package ch.sv7.tool.dbmanager.db.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipInputStream;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import ch.sv7.tool.dbmanager.DbManagerException;
import ch.sv7.tool.dbmanager.db.ColumnSchema;
import ch.sv7.tool.dbmanager.db.ColumnType;
import ch.sv7.tool.dbmanager.db.ColumnTypeManager;
import ch.sv7.tool.dbmanager.db.Field;
import ch.sv7.tool.dbmanager.db.FilePatternManager;
import ch.sv7.tool.dbmanager.db.Row;
import ch.sv7.tool.dbmanager.db.SafeString;
import ch.sv7.tool.dbmanager.db.Table;
import ch.sv7.tool.dbmanager.db.TableReader;
import ch.sv7.tool.dbmanager.db.TableSchema;
import ch.sv7.tool.dbmanager.db.TableWriter;

public class SaxXmlTableReader extends DefaultHandler implements TableReader {

	private String directory;

	private String schemaName;

	private String filePattern;

	private TableSchema schema;

	private int rowNb;

	private TableWriter writer;

	private boolean currentFieldIsNull;

	private boolean zippedFile;

	private Field currentField;
	
	private Attributes currentAttributes;

	private Row currentRow;

	private Table currentTable;

	private StringBuffer currentString;

	public SaxXmlTableReader(String directory, String schemaName, String filePattern, boolean zippedFile) {
		rowNb = 0;
		writer = null;
		currentFieldIsNull = false;
		currentField = null;
		currentRow = null;
		currentTable = null;
		currentString = null;
		this.directory = directory;
		this.schemaName = schemaName;
		this.filePattern = filePattern;
		this.zippedFile = zippedFile;
		rowNb = 0;
	}

	public Table readTable(TableSchema schema) throws DbManagerException {
		this.schema = schema;
		rowNb = 0;
		String filename = directory + System.getProperty("file.separator")
				+ FilePatternManager.getFilename(schema.getName(), schemaName, filePattern)
				+ (zippedFile ? ".zip" : ".xml");
		currentTable = new Table(schema.getName());
		writer = null;
		parseFile(filename);
		System.out.println("Number of rows readed from file : " + rowNb);
		return currentTable;
	}

	public void transferTable(TableSchema schema, TableWriter writer) throws DbManagerException {
		rowNb = 0;
		this.schema = schema;
		String filename = directory + System.getProperty("file.separator")
				+ FilePatternManager.getFilename(schema.getName(), schemaName, filePattern)
				+ (zippedFile ? ".zip" : ".xml");
		this.writer = writer;
		writer.writeHeader(schema);
		parseFile(filename);
		System.out.println("Number of rows readed from file : " + rowNb);
		writer.writeFooter(schema);
	}

	public void parseFile(String filename) throws DbManagerException {
		DefaultHandler handler = this;
		SAXParserFactory factory = SAXParserFactory.newInstance();
		try {
			SAXParser saxParser = factory.newSAXParser();
			if (zippedFile) {
				ZipInputStream zis = new ZipInputStream(new FileInputStream(filename));
				zis.getNextEntry();
				saxParser.parse(zis, handler);
			} else {
				saxParser.parse(new File(filename), handler);
			}
		} catch (ParserConfigurationException e) {
			throw new DbManagerException("ParseConfigurationException while parsing : " + filename, e);
		} catch (SAXException e) {
			if (e.getException() instanceof DbManagerException)
				throw (DbManagerException) e.getException();
			else
				throw new DbManagerException("Parsing error while parsing : " + filename, e);
		} catch (IOException e) {
			throw new DbManagerException("IOException while parsing file : " + filename, e);
		}
	}

	public void startDocument() throws SAXException {
	}

	public void endDocument() throws SAXException {
	}

	public void startElement(String namespaceURI, String sName, String qName, Attributes attrs) throws SAXException {
		currentAttributes = attrs;
		if (!"table".equalsIgnoreCase(qName))
			if ("row".equalsIgnoreCase(qName))
				currentRow = new Row();
			else if ("null".equalsIgnoreCase(qName)) {
				currentField.setValue(null);
				currentFieldIsNull = true;
			} else if ("field".equalsIgnoreCase(qName)) {
				currentField = new Field();
				currentField.setName(attrs.getValue("name"));
				currentString = new StringBuffer();
				currentFieldIsNull = false;
			}
	}

	public void endElement(String namespaceURI, String sName, String qName) throws SAXException {
		if (!"table".equalsIgnoreCase(qName))
			if ("row".equalsIgnoreCase(qName)) {
				rowNb++;
				if (currentTable != null)
					currentTable.add(currentRow);
				if (writer != null)
					try {
						writer.writeRow(currentRow, schema);
					} catch (DbManagerException e) {
						throw new SAXException(e);
					} catch (Throwable t) {
						throw new SAXException(new DbManagerException(t));
					}
			} else if ("field".equalsIgnoreCase(qName)) {
				if (currentFieldIsNull)
					currentField.setValue(null);
				else {
					String format = currentAttributes.getValue("format");
					try {
						currentField.setValue(
								getValueFromString(
										currentField.getName(), 
										currentString.toString(),
										format));
					} catch (DbManagerException e) {
						throw new SAXException(e);
					}
				}
				currentRow.addField(currentField);
			}
	}

	public void characters(char buf[], int offset, int len) throws SAXException {
		String data = new String(buf, offset, len);
		if (data.length() > 0 && currentString != null)
			currentString.append(data);
	}

	public Object getValueFromString(String fieldName, String fieldStringValue, String format) throws DbManagerException {
		ColumnSchema columnSchema = schema.getColumnSchema(fieldName);
		if (columnSchema == null) {
			throw new DbManagerException("No column " + fieldName + " found in structure of table "
					+ schema.getFullName());
		} else {
			ColumnType columnType = columnSchema.getType();
			return ColumnTypeManager.readFromString(new SafeString().setSafeString(fieldStringValue, format), columnType);
		}
	}
}
